package org.openmrs.authorization;

import java.io.StringReader;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import org.openmrs.Attribute;
import org.openmrs.AttributeValue;
import org.openmrs.User;
import org.openmrs.api.UserService;
import org.openmrs.api.context.Context;
import org.openmrs.util.ExternalServiceHandler;
import org.xacmlauth.core.context.AttributeType;
import org.xacmlauth.core.context.AttributeValueType;
import org.xacmlauth.core.context.DecisionType;
import org.xacmlauth.core.context.EnvironmentType;
import org.xacmlauth.core.context.RequestType;
import org.xacmlauth.core.context.ResourceType;
import org.xacmlauth.core.context.ResponseType;
import org.xacmlauth.core.context.ResultType;
import org.xacmlauth.core.context.SubjectType;
import org.xacmlauth.core.domain.EnforceInfo;
import org.xacmlauth.core.domain.EnforceRequest;
import org.xacmlauth.core.domain.EnforceRespInfo;
import org.xacmlauth.core.domain.EnforceResponse;
import org.xacmlauth.core.policy.ObligationType;
import org.xacmlauth.core.policy.ObligationsType;
import org.xacmlauth.util.ContextManager;

public class PEP {
	
	private static final String CLIENT_ID = "20";
	
	private List<String> obligationList = new ArrayList<String>();
	
	private static final ThreadLocal<PEP> threadLocal = new ThreadLocal<PEP>() {
		
		protected PEP initialValue() {
			return new PEP();
		}
	};
	
	private PEP() {
	}
	
	public static PEP getInstance() {
		return threadLocal.get();
	}
	
	public boolean isAuthorized(String priviledge, User user) throws Exception {
		obligationList = new ArrayList<String>();
		RequestType requestType = generateRequest(priviledge, user);
		
		EnforceRequest enforceRequest = new EnforceRequest();
		EnforceInfo enforceInfo = new EnforceInfo();
		enforceInfo.setClientID(CLIENT_ID);
		enforceInfo.setRequest(requestType);
		enforceRequest.setEnforceInfo(enforceInfo);
		
		JAXBContext domainContext = ContextManager.getDomainContext();
		Marshaller marshaller = domainContext.createMarshaller();
		
		StringWriter writer = new StringWriter();
		marshaller.marshal(enforceRequest, writer);
		String request = writer.toString();
		
		ExternalServiceHandler serviceHandler = ExternalServiceHandler.getInstance();
		String response = serviceHandler.callService(request);
		
		Unmarshaller unmarshaller = domainContext.createUnmarshaller();
		EnforceResponse enforceResponse = (EnforceResponse) unmarshaller.unmarshal(new StringReader(response));
		
		EnforceRespInfo repsonseInfo = enforceResponse.getRespInfo();
		
		boolean isAuthorized = false;
		if (repsonseInfo != null && repsonseInfo.getResponse() != null) {
			ResponseType resp = repsonseInfo.getResponse();
			List<ResultType> resultList = resp.getResults();
			ResultType result = resultList.get(0);
			
			if (result.getDecision().equals(DecisionType.PERMIT)) {
				isAuthorized = true;
				
				ObligationsType obligations = result.getObligations();
				
				if (obligations != null) {
					for (ObligationType obligation : obligations.getObligations()) {
						obligationList.add(obligation.getObligationId());
					}
				}
			}
			
		}
		
		return isAuthorized;
	}
	
	public List<String> getObligations() {
		return obligationList;
	}
	
	private RequestType generateRequest(String priviledge, User user) throws Exception {
		UserService userService = Context.getUserService();
		String userName = user.getUsername();
		String virtualRole = Context.getUserService().getVirtualRole(user);
		
		RequestType requestType = new RequestType();
		
		SubjectType subjectType = new SubjectType();
		
		AttributeType subjectRoleAttribute = new AttributeType();
		subjectRoleAttribute.setDataType("http://www.w3.org/2001/XMLSchema#string");
		subjectRoleAttribute.setAttributeId("opnmrs.virtual.role");
		AttributeValueType subjectRoleAttributeValue = new AttributeValueType();
		subjectRoleAttributeValue.getContent().add(virtualRole);
		subjectRoleAttribute.getAttributeValues().add(subjectRoleAttributeValue);
		subjectType.getAttributes().add(subjectRoleAttribute);
		
		AttributeType subjectUserAttribute = new AttributeType();
		subjectUserAttribute.setDataType("http://www.w3.org/2001/XMLSchema#string");
		subjectUserAttribute.setAttributeId("opnmrs.user.id");
		AttributeValueType subjectUserAttributeValue = new AttributeValueType();
		subjectUserAttributeValue.getContent().add(userName);
		subjectUserAttribute.getAttributeValues().add(subjectUserAttributeValue);
		subjectType.getAttributes().add(subjectUserAttribute);
		
		requestType.getSubjects().add(subjectType);
		
		ResourceType resourceType = new ResourceType();
		AttributeType resourceAttribute = new AttributeType();
		resourceAttribute.setAttributeId("openmrs.priviledge");
		resourceAttribute.setDataType("http://www.w3.org/2001/XMLSchema#string");
		
		AttributeValueType resourceAttributeValue = new AttributeValueType();
		resourceAttributeValue.getContent().add(priviledge);
		resourceAttribute.getAttributeValues().add(resourceAttributeValue);
		
		resourceType.getAttributes().add(resourceAttribute);
		requestType.getResources().add(resourceType);
		
		EnvironmentType environment = new EnvironmentType();
		AttributeType currentTime = new AttributeType();
		currentTime.setAttributeId("urn:oasis:names:tc:xacml:1.0:environment:current-time");
		currentTime.setDataType("http://www.w3.org/2001/XMLSchema#time");
		
		AttributeValueType timeValue = new AttributeValueType();
		
		SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
		String formatedTime = sdf.format(new Date());
		
		timeValue.getContent().add(formatedTime);
		currentTime.getAttributeValues().add(timeValue);
		
		environment.getAttributes().add(currentTime);
		requestType.setEnvironment(environment);
		
		List<Attribute> attributeList = userService.getAttributesByUser(user);
		
		List<AttributeValue> attributeValueList = userService.getAttributeValues(user, attributeList);
		Map<Attribute, AttributeValue> attributeValueMap = new HashMap<Attribute, AttributeValue>();
		
		for (AttributeValue attributeValue : attributeValueList) {
			attributeValueMap.put(attributeValue.getAttribute(), attributeValue);
		}
		
		for (Attribute attribute : attributeList) {
			AttributeType dynamicAttribute = new AttributeType();
			dynamicAttribute.setDataType(getXACMLDataType(attribute.getDataType()));
			dynamicAttribute.setAttributeId(attribute.getAttributeName());
			
			AttributeValue attributeValue = attributeValueMap.get(attribute);
			
			AttributeValueType dynamicAttributeValue = new AttributeValueType();
			dynamicAttributeValue.getContent().add(attributeValue.getAttributeValue());
			dynamicAttribute.getAttributeValues().add(dynamicAttributeValue);
			
			String ownerType = attribute.getOwnerType();
			if (ownerType.equals("subject")) {
				subjectType.getAttributes().add(dynamicAttribute);
			} else if (ownerType.equals("action")) {
				requestType.getAction().getAttributes().add(dynamicAttribute);
			} else if (ownerType.equals("environment")) {
				environment.getAttributes().add(dynamicAttribute);
			} else if (ownerType.equals("resource")) {
				resourceType.getAttributes().add(dynamicAttribute);
			}
			
		}
		
		updateDynanicAttributeValues(attributeValueList, userService);
		
		return requestType;
	}
	
	private void updateDynanicAttributeValues(List<AttributeValue> attributeValueList, UserService us) throws Exception {
		for (AttributeValue attribValue : attributeValueList) {
			Attribute attribute = attribValue.getAttribute();
			String dataType = attribute.getDataType();
			String currentValue = attribValue.getAttributeValue();
			String newValue = performOperation(dataType, currentValue);
			attribValue.setAttributeValue(newValue);
		}
		
		us.saveAttributeValues(attributeValueList);
	}
	
	private String getXACMLDataType(String dataType) throws Exception {
		String xacmlDataType = "";
		
		if (dataType.equals("integer")) {
			xacmlDataType = "http://www.w3.org/2001/XMLSchema#integer";
		}
		
		if (dataType.equals("double")) {
			xacmlDataType = "http://www.w3.org/2001/XMLSchema#double";
		}
		return xacmlDataType;
	}
	
	private String performOperation(String dataType, String currentValue) throws Exception {
		String nextValue = "";
		if (dataType.equals("integer")) {
			int currentVal = Integer.parseInt(currentValue);
			int nextVal = currentVal + 1;
			nextValue = String.valueOf(nextVal);
		} else if (dataType.equals("double")) {
			double currentVal = Double.parseDouble(currentValue);
			double nextval = currentVal + 1;
			nextValue = String.valueOf(nextval);
		} else {
			throw new Exception("Unsupported dynamic attribute type");
		}
		
		return nextValue;
		
	}
	
}
